import * as THREE from '../../build/three.module.js';
import Stats from '../../jsm/libs/stats.module.js';
import { GUI } from '../../jsm/libs/dat.gui.module.js';
import { OrbitControls } from '../../jsm/controls/OrbitControls.js';
import { EXRLoader } from '../../jsm/loaders/EXRLoader.js';
import { PMREMGenerator } from '../../jsm/pmrem/PMREMGenerator.js';
import { PMREMCubeUVPacker } from '../../jsm/pmrem/PMREMCubeUVPacker.js';

// gui参数
const params = {
  envMap: 'EXR',
  roughness: 0,
  metalness: 0,
  exposure: 1.0,
  debug: false
}

// 场景
const scene = new THREE.Scene();

// 相机
const camera = new THREE.PerspectiveCamera(40, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 120);

// 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.toneMapping = THREE.LinearToneMapping;
renderer.setPixelRatio(devicePixelRatio);
renderer.setSize(innerWidth, innerHeight);
renderer.gammaInput = false;
renderer.gammaOutput = true;
document.body.appendChild(renderer.domElement);

// Mesh
const geo = new THREE.TorusKnotBufferGeometry(18, 8, 150, 20);
const mtl = new THREE.MeshStandardMaterial({
  metalness: params.metalness,
  roughness: params.roughness,
  emvMapIntensity: 1.0
})
const torusMesh = new THREE.Mesh(geo, mtl);
scene.add(torusMesh);

// Debug 平面
const planeGeo = new THREE.PlaneBufferGeometry(200, 200);
const planeMtl = new THREE.MeshBasicMaterial();
const planeMesh = new THREE.Mesh(planeGeo, planeMtl);
planeMesh.position.y = -50;
planeMesh.rotation.x = -0.5 * Math.PI;
scene.add(planeMesh);

// 背景贴图和渲染目标
let exrBackground, exrCubeRenderTarget, pngBackground, pngCubeRenderTarget;

// 异步贴图加载
new EXRLoader()
  .setDataType(THREE.FloatType)
  .load('../../textures/piz_compressed.exr', texture => {
    const options = {
      generateMipmaps: false,
      minFilter: THREE.LinearFilter,
      magFilter: THREE.LinearFilter
    }

    exrBackground = new THREE.WebGLRenderTargetCube(512, 512, options).fromEquirectangularTexture(renderer, texture);

    // 预处理
    const pmremGenerator = new PMREMGenerator(exrBackground.texture);
    pmremGenerator.update(renderer);

    const pmremCubeUVPacker = new PMREMCubeUVPacker(pmremGenerator.cubeLods);
    pmremCubeUVPacker.update(renderer)

    exrCubeRenderTarget = pmremCubeUVPacker.CubeUVRenderTarget;

    texture.dispose();
    pmremGenerator.dispose();
    pmremCubeUVPacker.dispose();
  })

new THREE.TextureLoader().load('../../textures/equirectangular.png', texture => {
  texture.encoding = THREE.sRGBEncoding;

  pngBackground = new THREE.WebGLRenderTargetCube(512, 512).fromEquirectangularTexture(renderer, texture);

    // 预处理
    const pmremGenerator = new PMREMGenerator(pngBackground.texture);
    pmremGenerator.update(renderer);

    const pmremCubeUVPacker = new PMREMCubeUVPacker(pmremGenerator.cubeLods);
    pmremCubeUVPacker.update(renderer)

    pngCubeRenderTarget = pmremCubeUVPacker.CubeUVRenderTarget;

    texture.dispose();
    pmremGenerator.dispose();
    pmremCubeUVPacker.dispose();
})

// 性能记录
const stats = new Stats();
document.body.appendChild(stats.dom);

// 控制器 
const controls = new OrbitControls(camera, renderer.domElement);
controls.minDistance = 50;
controls.maxDistance = 300;

// 事件处理
const handleWindowResize = () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(innerWidth, innerHeight);
}
window.addEventListener('resize', handleWindowResize);

// 渲染函数
const render = () => {
  torusMesh.material.roughness = params.roughness;
  torusMesh.material.metalness = params.metalness;

  let newEnvMap = torusMesh.material.envMap;
  let background = scene.background;

  switch (params.envMap) {
    case 'EXR':
      newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null;
      background = exrBackground;
      break;
    case 'PNG':
      newEnvMap = pngCubeRenderTarget ? pngCubeRenderTarget.texture : null;
      background = pngBackground;
      break;
    default: break;
  }

  if (newEnvMap !== torusMesh.material.envMap) {
    torusMesh.material.envMap = newEnvMap;
    torusMesh.material.needsUpdate = true;

    planeMesh.material.map = newEnvMap;
    planeMesh.material.needrsUpdate = true;
  }

  torusMesh.rotation.y += 0.005;
  planeMesh.visible = params.debug;

  scene.background = background;
  renderer.toneMappingExposure = params.exposure;

  renderer.render(scene, camera);
}

// 驱动函数
const animate = () => {
  requestAnimationFrame(animate);

  stats.begin();
  render();
  stats.end();
}
animate();

// GUI
const gui = new GUI();
gui.add(params, 'envMap', ['EXR', 'PNG']);
gui.add(params, 'roughness', 0, 1, 0.01);
gui.add(params, 'metalness', 0, 1, 0.01);
gui.add(params, 'exposure', 0, 2, 0.01);
gui.add(params, 'debug', false);
gui.open();
